﻿using System.Collections.Generic;
using System.IO;
using UnityEngine;
using Unity.Mathematics;
using System;
using Object = UnityEngine.Object;

namespace Jinndev {

    public static class ResourceManager {

        public const string ANDROID_STREAMIMG_PROTOCAL = @"jar:file://"; //Android下StreamingAssetPath需添加的协议

        public struct MaterialKeys {
            public string mainTex;
            public string mainColor;
        }

        public const string DEFAULT = "Default";

        private class MaterialTemplate {
            public Material material;
            public MaterialKeys keys;
            public MaterialTemplate(Material material, MaterialKeys keys) {
                this.material = material;
                this.keys = keys;
            }
        }

        private static Dictionary<string, MaterialTemplate> materialTemplates = new Dictionary<string, MaterialTemplate>();

        private static Dictionary<string, Mesh> meshCache = new Dictionary<string, Mesh>();
        private static Dictionary<int, Material> materialCache = new Dictionary<int, Material>();

        // 加载资源缓存，用于避免重复创建同一个Texture2D，可以在全部加载完毕后清除此缓存
        private static Dictionary<string, Object> loadCache = new Dictionary<string, Object>();

        public static bool Initialized { get; private set; }

        public static void Init() {
            if (!Initialized) {
                BetterStreamingAssets.Initialize();
                Initialized = true;
            }
        }

        public static void AddMaterialTemplate(string templateName, Material material, MaterialKeys keys = default) {
            if (!materialTemplates.ContainsKey(templateName)) {
                materialTemplates.Add(templateName, new MaterialTemplate(material, keys));
            }
            else {
                Debug.LogError($"{templateName} already exists");
            }
        }

        public static void SetDefaultMaterialTemplate(Material material, MaterialKeys keys = default) {
            AddMaterialTemplate(DEFAULT, material, keys);
        }

        /// <summary>
        /// 清除加载资源缓存，可以在加载完全部资源后执行
        /// </summary>
        public static void ClearLoadCache() {
            loadCache.Clear();
        }

        /// <summary>
        /// 根据模板创建新材质，并不放入缓存
        /// </summary>
        /// <param name="templateName"></param>
        /// <param name="mainTexture"></param>
        /// <returns></returns>
        public static Material CreateMaterial(string templateName, Texture2D mainTexture) {
            MaterialTemplate template;
            Material material;
            if (materialTemplates.TryGetValue(templateName, out template)) {
                material = new Material(template.material);
            }
            else if (materialTemplates.TryGetValue(DEFAULT, out template)) {
                Debug.LogError($"Template not found: {templateName}, using default");
                material = new Material(template.material);
            }
            else {
                Debug.LogError($"Template not found: {templateName}");
                return null;
            }
            CommonUtil.SetMainTexture(material, mainTexture, template.keys.mainTex);
            return material;
        }

        /// <summary>
        /// 从缓存中得到共享的材质，如果没有则创建一个，并加入缓存
        /// </summary>
        /// <param name="templateName"></param>
        /// <param name="mainTexture"></param>
        /// <returns></returns>
        public static Material GetSharedMaterial(string templateName, Texture2D mainTexture) {
            int hash = 0;
            if (templateName != null) {
                hash ^= templateName.GetHashCode();
            }
            if (mainTexture != null) {
                hash ^= mainTexture.GetHashCode();
            }

            Material material;
            if (materialCache.TryGetValue(hash, out material)) {
                return material;
            }
            material = CreateMaterial(templateName, mainTexture);
            materialCache.Add(hash, material);
            return material;
        }

        /// <summary>
        /// 得到指定大小和锚点的矩形mesh
        /// </summary>
        /// <param name="name"></param>
        /// <param name="size"></param>
        /// <param name="pivot"></param>
        /// <returns></returns>
        public static Mesh GetSharedRectMesh(string name, float2 size, float2 pivot) {
            string key = string.Format("{0}_{1}x{2}_{3}x{4}", name, size.x, size.y, pivot.x, pivot.y);

            Mesh mesh;
            if (meshCache.TryGetValue(key, out mesh)) {
                return mesh;
            }
            else {
                mesh = MeshUtil.CreateRectMesh(size, pivot);
                meshCache.Add(key, mesh);
                return mesh;
            }
        }

        /// <summary>
        /// 从mod中加载资源
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="rootPath">mod的相对路径</param>
        /// <param name="resPath">资源的相对路径</param>
        /// <returns></returns>
        public static T Load<T>(string rootPath, string resPath) where T : Object {
            if (resPath.StartsWith("res:")) {
                return Resources.Load<T>(resPath.Substring(4));
            }
            else {
                resPath = Path.Combine(rootPath, resPath);
                if (loadCache.TryGetValue(resPath, out Object obj) && obj is T) {
                    return obj as T;
                }

                if (!FileExists(resPath)) {
                    Debug.LogError($"File not found: {resPath}");
                    return null;
                }

                try {
                    if (typeof(T) == typeof(Texture2D)) {
                        //Texture2D texture = new Texture2D(0, 0, TextureFormat.ARGB32, false, false);
                        Texture2D texture = new Texture2D(0, 0);
                        texture.filterMode = FilterMode.Point;
                        texture.wrapMode = TextureWrapMode.Repeat;
                        texture.name = Path.GetFileNameWithoutExtension(resPath);

                        byte[] bytes = ReadAllBytes(resPath);
                        if (bytes == null || bytes.Length == 0) {
                            Debug.LogError($"Image load fail: {resPath}");
                            return null;
                        }

                        bool loaded = texture.LoadImage(bytes, false);
                        if (!loaded) {
                            Debug.LogError($"Image load fail: {resPath}");
                            return null;
                        }

                        texture.Compress(true);
                        texture.Apply(false, true);
                        loadCache.Add(resPath, texture);
                        return texture as T;
                    }
                    else {
                        Debug.LogError($"Invalid resource type: {typeof(T)}");
                        return null;
                    }
                }
                catch (Exception e) {
                    Debug.LogException(e);
                    return null;
                }
            }
        }

        public static bool FileExists(string file) {
            if (file.StartsWith(ANDROID_STREAMIMG_PROTOCAL)) {
                file = file.Substring(Application.streamingAssetsPath.Length);
                return BetterStreamingAssets.FileExists(file);
            }
            return File.Exists(file);
        }

        public static bool DirectoryExists(string dir) {
            if (dir.StartsWith(ANDROID_STREAMIMG_PROTOCAL)) {
                dir = dir.Substring(Application.streamingAssetsPath.Length);
                return BetterStreamingAssets.DirectoryExists(dir);
            }
            return Directory.Exists(dir);
        }

        public static byte[] ReadAllBytes(string file) {
            if (file.StartsWith(ANDROID_STREAMIMG_PROTOCAL)) {
                file = file.Substring(Application.streamingAssetsPath.Length);
                return BetterStreamingAssets.ReadAllBytes(file);
            }
            return File.ReadAllBytes(file);
        }

        public static string ReadAllText(string file) {
            if (file.StartsWith(ANDROID_STREAMIMG_PROTOCAL)) {
                file = file.Substring(Application.streamingAssetsPath.Length);
                return BetterStreamingAssets.ReadAllText(file);
            }
            return File.ReadAllText(file);
        }

        public static string[] GetFiles(string dir) {
            return GetFiles(dir, null, SearchOption.TopDirectoryOnly);
        }

        public static string[] GetFiles(string dir, string searchPattern, SearchOption searchOption = SearchOption.TopDirectoryOnly) {
            if (dir.StartsWith(ANDROID_STREAMIMG_PROTOCAL)) {
                dir = dir.Substring(Application.streamingAssetsPath.Length);
                string[] files = BetterStreamingAssets.GetFiles(dir, searchPattern, searchOption);
                for (int i = 0; i < files.Length; i++) {
                    files[i] = Application.streamingAssetsPath + files[i];
                }
                return files;
            }
            if (searchPattern == null) {
                return Directory.GetFiles(dir);
            }
            else {
                return Directory.GetFiles(dir, searchPattern, searchOption);
            }
        }

        public static Stream OpenRead(string file) {
            if (file.StartsWith(ANDROID_STREAMIMG_PROTOCAL)) {
                file = file.Substring(Application.streamingAssetsPath.Length);
                return BetterStreamingAssets.OpenRead(file);
            }
            return File.OpenRead(file);
        }

        public static void CopyStreamingAssetsDirToPersistentDataPath(string dir) {
            string persistentDataPath = Application.persistentDataPath;
            byte[] buffer = new byte[1024 * 32];
            string[] files = BetterStreamingAssets.GetFiles(dir, "*", SearchOption.AllDirectories);
            foreach (string file in files) {
                string newFile = Path.Combine(persistentDataPath, file);
                CommonUtil.PrepareFileDir(newFile);

                FileStream output = null;
                Stream input = null;
                try {
                    output = File.OpenWrite(newFile);
                    input = BetterStreamingAssets.OpenRead(file);

                    int length = input.Read(buffer, 0, buffer.Length);
                    if (length > 0) {
                        output.Write(buffer, 0, length);
                    }
                    output.Flush();
                }
                catch (Exception e) {
                    throw e;
                }
                finally {
                    output?.Dispose();
                    input?.Dispose();
                }
            }
        }

        //public static void Load() {
        //    UnityWebRequest req = UnityWebRequest.Get()
        //}

    }

}
